// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Linq;
using System.Xml;
using ILLink.Shared;
using Mono.Cecil;
using Mono.Linker.Steps;

namespace Mono.Linker
{
    public class EmbeddedXmlInfo
    {
        static EmbeddedResource? GetEmbeddedXml(AssemblyDefinition assembly, Func<Resource, bool> predicate)
        {
            return assembly.Modules
                .SelectMany(mod => mod.Resources)
                .Where(res => res.ResourceType == ResourceType.Embedded)
                .Where(res => res.Name.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
                .Where(res => predicate(res))
                .SingleOrDefault() as EmbeddedResource;
        }

        public void ProcessDescriptors(AssemblyDefinition assembly, LinkContext context)
        {
            if (context.Annotations.GetAction(assembly) == AssemblyAction.Skip)
                return;

            var rsc = GetEmbeddedXml(assembly, res => ShouldProcessRootDescriptorResource(assembly, context, res.Name));
            if (rsc == null)
                return;

            DescriptorMarker? marker = null;
            try
            {
                context.LogMessage($"Processing embedded trimming descriptor '{rsc.Name}' from '{assembly.Name}'.");
                marker = GetExternalResolveStep(context, rsc, assembly);
            }
            catch (XmlException ex)
            {
                /* This could happen if some broken XML file is embedded. */
                context.LogError(null, DiagnosticId.XmlException, rsc.Name, ex.ToString());
            }

            marker?.Mark();
        }

        public SubstitutionInfo? ProcessSubstitutions(AssemblyDefinition assembly, LinkContext context)
        {
            if (context.Annotations.GetAction(assembly) == AssemblyAction.Skip)
                return null;

            var rsc = GetEmbeddedXml(assembly, res => res.Name.Equals("ILLink.Substitutions.xml", StringComparison.OrdinalIgnoreCase));
            if (rsc == null)
                return null;

            BodySubstitutionParser? parser = null;
            try
            {
                context.LogMessage($"Processing embedded substitution descriptor '{rsc.Name}' from '{assembly.Name}'.");
                parser = GetExternalSubstitutionParser(context, rsc, assembly);
            }
            catch (XmlException ex)
            {
                context.LogError(null, DiagnosticId.XmlException, rsc.Name, ex.ToString());
            }

            if (parser == null)
                return null;

            var substitutionInfo = new SubstitutionInfo();
            parser.Parse(substitutionInfo);
            return substitutionInfo;
        }

        public AttributeInfo? ProcessAttributes(AssemblyDefinition assembly, LinkContext context)
        {
            if (context.Annotations.GetAction(assembly) == AssemblyAction.Skip)
                return null;

            var rsc = GetEmbeddedXml(assembly, res => res.Name.Equals("ILLink.LinkAttributes.xml", StringComparison.OrdinalIgnoreCase));
            if (rsc == null)
                return null;

            LinkAttributesParser? parser = null;
            try
            {
                context.LogMessage($"Processing embedded '{rsc.Name}' from '{assembly.Name}'.");
                parser = GetExternalLinkAttributesParser(context, rsc, assembly);
            }
            catch (XmlException ex)
            {
                context.LogError(null, DiagnosticId.XmlException, rsc.Name, ex.ToString());
            }

            if (parser == null)
                return null;

            var attributeInfo = new AttributeInfo();
            parser.Parse(attributeInfo);
            return attributeInfo;
        }

        static string GetAssemblyName(string descriptor)
        {
            int pos = descriptor.LastIndexOf('.');
            if (pos == -1)
                return descriptor;

            return descriptor.Substring(0, pos);
        }

        static bool ShouldProcessRootDescriptorResource(AssemblyDefinition assembly, LinkContext context, string resourceName)
        {
            if (resourceName.Equals("ILLink.Descriptors.xml", StringComparison.OrdinalIgnoreCase))
                return true;

            if (GetAssemblyName(resourceName) != assembly.Name.Name)
                return false;

            switch (context.Annotations.GetAction(assembly))
            {
                case AssemblyAction.Link:
                case AssemblyAction.AddBypassNGen:
                case AssemblyAction.AddBypassNGenUsed:
                case AssemblyAction.Copy:
                    return true;
                default:
                    return false;
            }
        }

        protected virtual DescriptorMarker GetExternalResolveStep(LinkContext context, EmbeddedResource resource, AssemblyDefinition assembly)
        {
            return new DescriptorMarker(context, resource.GetResourceStream(), resource, assembly, "resource " + resource.Name + " in " + assembly.FullName);
        }

        protected virtual BodySubstitutionParser GetExternalSubstitutionParser(LinkContext context, EmbeddedResource resource, AssemblyDefinition assembly)
        {
            return new BodySubstitutionParser(context, resource.GetResourceStream(), resource, assembly, "resource " + resource.Name + " in " + assembly.FullName);
        }

        protected virtual LinkAttributesParser GetExternalLinkAttributesParser(LinkContext context, EmbeddedResource resource, AssemblyDefinition assembly)
        {
            return new LinkAttributesParser(context, resource.GetResourceStream(), resource, assembly, "resource " + resource.Name + " in " + assembly.FullName);
        }
    }
}
